package com.gt.wxpay.cashier.payment.application.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gt.wxpay.cashier.common.LocalTimeUtils;
import com.gt.wxpay.cashier.common.RedisLockUtils;
import com.gt.wxpay.cashier.common.RuleCommonUilt;
import com.gt.wxpay.cashier.domain.Result;
import com.gt.wxpay.cashier.domain.dto.PayRespDto;
import com.gt.wxpay.cashier.domain.dto.QueryOrderDto;
import com.gt.wxpay.cashier.domain.dto.QueryRefundDto;
import com.gt.wxpay.cashier.domain.model.*;
import com.gt.wxpay.cashier.merchantNew.application.MerchantService;
import com.gt.wxpay.cashier.merchantNew.domain.model.Merchant;
import com.gt.wxpay.cashier.payChannel.application.PayChannelService;
import com.gt.wxpay.cashier.payChannel.application.WxPayService;
import com.gt.wxpay.cashier.payChannel.domain.model.PayChannel;
import com.gt.wxpay.cashier.payment.application.CheckCommonService;
import com.gt.wxpay.cashier.payment.application.PaymentService;
import com.gt.wxpay.cashier.payChannel.callback.vo.WxPayRespDto;
import com.gt.wxpay.cashier.payment.infra.persistent.PaymentMapper;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 支付记录服务实现类
 *
 * @author cztech
 */
@Slf4j
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, CashierPayment> implements PaymentService {

    @Resource
    private PaymentMapper paymentMapper;

    @Resource
    private WxPayService wxPayService;

    @Autowired
    private CheckCommonService checkCommonService;

    /**
     * 商户信息服务实现
     */
    @Autowired
    private MerchantService merchantService;


    /**
     * 渠道
     */
    @Autowired
    private PayChannelService payChannelService;




    /**
     * 下单
     *
     * @return json格式的返回值
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayRespDto order(CashierPayment cashierPayment) {
        String channel = cashierPayment.getPayChannel();
        // 渠道规则
        if (!PayTypeEnum.WECHAT.getDesc().equals(channel)) {
            throw new RuntimeException("目前只支持微信支付渠道");
        }
        // 微信支付 校验必要参数
        String checkMsg = checkCommonService.payOrderCreateCheck(cashierPayment);
        if (checkMsg != null) {
            throw new RuntimeException(checkMsg);
        }
        // 获取系统的商户信息{本系统的商户信息|非微信支付的商户}
        String merchantId = cashierPayment.getMerchantId();
        // 根据商户号获取本系统的商户详情
        Merchant thisMerchant = merchantService.getThatMerchantNo(merchantId);
        // 商户手续费= 商户收入金额*商户手续费率(千分位)
        Integer fee = thisMerchant.getFee();
        // 计算商户的手续费(Amount单位是分)
        BigDecimal merchantFee = new BigDecimal((cashierPayment.getAmount() / 1000) * fee);
        cashierPayment.setMerchantFee(merchantFee);
        // 下单之前是未支付
        cashierPayment.setStatus(PaymentStatusEnum.UNPAY.code());
        // 支付用户信息{用户的ID|用户的手机号}
        cashierPayment.setPayerId(Long.parseLong(cashierPayment.getUserId()));
        cashierPayment.setPayerName(cashierPayment.getPhoneNo());
        // 校验支付订单是否已存在{判断同一个商户和同一个订单号是否已存在}||使用分布式锁判断订单唯一
        Boolean lock = RedisLockUtils.lock(RuleCommonUilt.PAY_ORDER_LOCK_PREFIX + cashierPayment.getMerchantId() + "_" + cashierPayment.getOrderNo(), "1", 60);
        if (!lock) {
            throw new RuntimeException("支付订单已创建,请勿重复下单!!!");
        }
        // 优先使用payment的交易类型(支付)
        cashierPayment.setTransactionType(TransactionTypeEnum.PAY.code());

        // 默认CNY
        cashierPayment.setCurrencyType(StringUtils.isEmpty(cashierPayment.getCurrencyType()) ? "CNY" : cashierPayment.getCurrencyType());

        // 生成支付订单号（雪花算法）
        cashierPayment.setPaymentNo(RuleCommonUilt.getOrderNoNextValue());

        // 生成微信支付订单====调用底层微信渠道侧：进入微信下单
        WxPayRespDto wxRespDto = wxPayService.unifiedOrderV3(cashierPayment);

        // 保存支付信息(预下单流水号)
        String prepayId = wxRespDto.getPrepayId().split("=")[1];
        cashierPayment.setChannelFlowNo(prepayId);
        // 保存二维码链接，支持继续支付
        cashierPayment.setCodeUrl(wxRespDto.getCodeUrl());
        this.save(cashierPayment);
        // 程序下单完成就解除分布式锁
        RedisLockUtils.unlock(RuleCommonUilt.PAY_ORDER_LOCK_PREFIX + cashierPayment.getMerchantId() + "_" + cashierPayment.getOrderNo(), "1");
        return new PayRespDto()
                .setPaymentNo(wxRespDto.getPaymentNo())
                .setNonceStr(wxRespDto.getNonceStr())
                .setCodeUrl(wxRespDto.getCodeUrl())
                .setTimeStamp(wxRespDto.getTimeStamp())
                .setSignType(wxRespDto.getSignType())
                .setPrepayId(prepayId)
                .setPackgeVal(wxRespDto.getPrepayId())
                .setPaySign(wxRespDto.getPaySign());

    }

    @Override
    public CashierPayment queryByPamentNo(String paymentNo) {
        return paymentMapper.getByPamentNo(paymentNo);
    }

    @Override
    public CashierPayment queryByPamentNo(String paymentNo, Integer code) {
        return paymentMapper.getByPamentNoByType(paymentNo, code);
    }

    /**
     * 远程调用微信侧更新状态--返回
     *
     * @param paymentNo
     * @return
     */
    @Override
    @SuppressWarnings("all")
    public QueryOrderDto queryInfo(String paymentNo) {
        // 获取当前订单流水
        CashierPayment payment = paymentMapper.getByPamentNo(paymentNo);
        if (payment == null) {
            throw new RuntimeException(RuleCommonUilt.OPERATION_FAIL_BY_PAY_ORDER_NOT_FOUND);
        }
        // 判断是否是支付中的数据
        if (PaymentStatusEnum.UNPAY.code().equals(payment.getStatus()) || PaymentStatusEnum.PAYING.code().equals(payment.getStatus())) {
            String payChannelId = payment.getPayChannelId();
            // 获取渠道信息
            PayChannel payChannel = payChannelService.getById(Long.valueOf(payChannelId));
            // 判断是不是微信渠道
            if (payChannel != null && payChannel.getPayChannel() == PayTypeEnum.WECHAT.getCode()) {
                // 调用底层微信API查询流水单
                CashierPayment wxRespDto = wxPayService.queryOrderInfo(payment);
                // 更新保存
                this.updateById(wxRespDto);
                return QueryOrderDto.builder().paymentNo(wxRespDto.getPaymentNo())
                        .paySuccessTime(DateUtil.toLocalDateTime(wxRespDto.getSuccessTime()))
                        .amount(wxRespDto.getAmount())
                        .payStatus(wxRespDto.getStatus())
                        .payStatusDesc(wxRespDto.getChannelReturnMessage())
                        .refundTime(wxRespDto.getRefundTime() == null ? null : DateUtil.toLocalDateTime(wxRespDto.getRefundTime()))
                        .closeTime(Objects.equals(wxRespDto.getStatus(), PaymentStatusEnum.CLOSED.code()) ? DateUtil.toLocalDateTime(wxRespDto.getSuccessTime()) : null)
                        .build();
            } else {
                assert payChannel != null;
                throw new RuntimeException(RuleCommonUilt.NOT_SUPPER_TYPE + payChannel.getPayChannelName());
            }
        }
        return QueryOrderDto.builder().paymentNo(payment.getPaymentNo())
                .paySuccessTime(DateUtil.toLocalDateTime(payment.getSuccessTime()))
                .amount(payment.getAmount())
                .payStatus(payment.getStatus())
                .payStatusDesc(payment.getChannelReturnMessage())
                .refundTime(payment.getRefundTime() == null ? null : DateUtil.toLocalDateTime(payment.getRefundTime()))
                .closeTime(Objects.equals(payment.getStatus(), PaymentStatusEnum.CLOSED.code()) ? DateUtil.toLocalDateTime(payment.getSuccessTime()) : null)
                .build();
    }

    @Override
    public Result<QueryRefundDto> refund(CashierPayment cashierPayment) {
        // 创建退款记录
        Refund refund = wxPayService.refundV3(cashierPayment);
        // 渠道侧的【微信支付退款号】 微信支付退款号
        cashierPayment.setChannelRefundFlowNo(refund.getRefundId());
        // 设置退款状态
        cashierPayment.setChannelRefundResultCode(refund.getStatus().name());
        // 【原微信支付订单号】 微信支付交易订单号
        cashierPayment.setChannelFlowNo(refund.getTransactionId());
        cashierPayment.setChannelRefundResultMessage(refund.getAmount().toString());
        cashierPayment.setChannelRefundType(refund.getChannel().name());
        cashierPayment.setChannelRefundStatus(refund.getStatus().name());
        cashierPayment.setChannelRefundCreateTime(LocalTimeUtils.dealFormatWithWx(refund.getCreateTime()));
        cashierPayment.setChannelRefundFundsAccount(refund.getFundsAccount().name());
        // SUCCESS - 退款成功，退款状态 CLOSED - 退款关闭，退款状态 PROCESSING - 退款处理中，退款状态 ABNORMAL - 退款异常，退款状态
        switch (refund.getStatus()) {
            case SUCCESS:
                log.info("退款成功......");
                cashierPayment.setStatus(RefundStatusEnum.SUCCESS.getCode());
                cashierPayment.setChannelRefundSuccessTime(LocalTimeUtils.dealDateFormatWithWx(refund.getSuccessTime()));
                break;
            case CLOSED:
                log.info("退款已关闭......");
                cashierPayment.setStatus(RefundStatusEnum.CLOSED.getCode());
                break;
            case PROCESSING:
                log.info("退款处理中......");
                cashierPayment.setStatus(RefundStatusEnum.PROCESSING.getCode());
                break;
            case ABNORMAL:
                log.error("退款异常......");
                cashierPayment.setStatus(RefundStatusEnum.ABNORMAL.getCode());
                break;
            default:
                break;
        }
        // 正常处理流程
        this.updateById(cashierPayment);
        // 分布式锁释放
        RedisLockUtils.unlock(RuleCommonUilt.PAY_REFUND_LOCK_PREFIX + cashierPayment.getPaymentNo(), "1");
        Result<QueryRefundDto> ok = Result.ok();
        QueryRefundDto refundNoDto = new QueryRefundDto();
        refundNoDto.setStatus(cashierPayment.getStatus());
        refundNoDto.setRefundNo(cashierPayment.getPaymentNo());
        ok.setResponse(refundNoDto);
        return ok;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result<String> closeOrder(String paymentNo) {
        // 分布式锁
        Boolean lock = RedisLockUtils.lock(RuleCommonUilt.CLOSE_ORDER_LOCK_PREFIX + paymentNo, "1", 60);
        if (!lock) {
            throw new RuntimeException("支付订单正在关闭,请勿重复点击关闭!!!");
        }
        // 获取本地收银台订单信息
        CashierPayment cashierPayment = paymentMapper.getByPamentNo(paymentNo);
        if (cashierPayment == null) {
            throw new RuntimeException("错误的支付单号:" + paymentNo);
        }

        String checkMsg = checkCommonService.payOrderCloseCheck(cashierPayment);
        if (checkMsg != null) {
            throw new RuntimeException(checkMsg);
        }
        // 关闭支付
        wxPayService.closePayment(cashierPayment);
        cashierPayment.setStatus(PaymentStatusEnum.CLOSED.code());
        cashierPayment.setChannelReturnCode(PaymentStatusEnum.CLOSED.name());
        cashierPayment.setChannelReturnMessage("订单已关闭");
        updateById(cashierPayment);
        // 解除分布式锁
        RedisLockUtils.unlock(RuleCommonUilt.CLOSE_ORDER_LOCK_PREFIX + paymentNo, "1");
        return Result.success();
    }

    /**
     * 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，建议查询退款状态在提交退款申请后1分钟发起，一般来说零钱支付的退款5分钟内到账，银行卡支付的退款1-3个工作日到账。
     *
     * @param refundOrder refundOrder
     * @return QueryRefundDto
     */
    @Override
    public QueryRefundDto queryRefund(CashierPayment refundOrder) {
        String payChannelId = refundOrder.getPayChannelId();
        // 获取渠道信息
        PayChannel payChannel = payChannelService.getById(Long.valueOf(payChannelId));
        // 判断支付渠道
        if (payChannel != null && PayTypeEnum.WECHAT.getCode() == payChannel.getPayChannel()) {
            // 查询
            QueryRefundDto refundDto = wxPayService.queryRefund(refundOrder);
            // 更新状态
            updateById(refundOrder);
            return refundDto;
        }
        throw new RuntimeException(RuleCommonUilt.NOT_SUPPER_TYPE);
    }

    @Override
    public List<CashierPayment> listAllByThisDayAndStatusNotSuccessOrNotFail(String currentTime, List<Integer> whereIn) {
        return paymentMapper.listAllByThisDayAndStatusNotSuccessOrNotFail(currentTime, whereIn);
    }

    @Override
    public List<CashierPayment> listNotEndingOrderByPage(String currentTime, String timeOutValue, List<Integer> whereIn, IPage<CashierPayment> page) {
        return paymentMapper.listNotEndingOrderByPage(currentTime, timeOutValue, whereIn, page);
    }

    @Override
    public List<CashierPayment> listNeedCloseOrderByPage(String timeOutValue, List<Integer> whereIn, IPage<CashierPayment> page) {
        return paymentMapper.listNeedCloseOrderByPage(timeOutValue, whereIn, page);
    }

    /**
     * 获取商户侧退款失败的订单
     *
     * @param merchantRefundAbnormalValue merchantRefundAbnormalValue
     * @param transactionTypeValue        transactionTypeValue
     * @return CashierPayment
     */
    @Override
    public List<CashierPayment> listAllByMerchantRefundFailTry(Integer merchantRefundAbnormalValue, Integer transactionTypeValue) {
        return paymentMapper.listAllByMerchantRefundFailTry(merchantRefundAbnormalValue, transactionTypeValue);
    }

    @Override
    public List<CashierPayment> listAllByThatMerchantFailRefundOrder(String merchantId, Integer merchantRefundAbnormalValue, Integer transactionTypeValue) {
        return paymentMapper.listAllByThatMerchantFailRefundOrder(merchantId, merchantRefundAbnormalValue, transactionTypeValue);
    }

    @Override
    public CashierPayment getByPamentNo(String paymentNo) {
        return paymentMapper.getByPamentNo(paymentNo);
    }

}
